#!/usr/bin/env python
###############################################################################
# Top contributors (to current version):
#   Andres Noetzli, Gereon Kremer, Makai Mann
#
# This file is part of the cvc5 project.
#
# Copyright (c) 2009-2022 by the authors listed in the file AUTHORS
# in the top-level source directory and their institutional affiliations.
# All rights reserved.  See the file COPYING in the top-level source
# directory for licensing information.
# #############################################################################
##
"""
This script reads a file that defines enums and generates .pxd and .pxi files
which declare all the enum values and implement a Python wrapper for values,
respectively. The default names are kinds.pxd / kinds.pxi, but the name is
configurable from the command line with --enums-file-prefix.

The script is aware of the '#if 0' pattern and will ignore values declared
between '#if 0' and '#endif'. It can also handle nested '#if 0' pairs.
"""

import argparse
import os
import re
import sys

# get access to cvc5/src/api/parseenums.py
SOURCE_DIR = '${CMAKE_SOURCE_DIR}/src'
sys.path.insert(0, os.path.abspath(f'{SOURCE_DIR}/api'))

from parseenums import *

#################### Default Filenames ################
DEFAULT_PREFIX = 'kinds'

################ Comments and Macro Tokens ############
PYCOMMENT = '#'

ENUMS_PXD_TOP_TEMPLATE = \
r"""cdef extern from "{header}" namespace "{namespace}":
    cdef enum {enum}:
"""

ENUMS_PXI_TOP = \
r'''# distutils: language = c++
# distutils: extra_compile_args = -std=c++11

from {basename} cimport {enum} as c_{enum}
from enum import Enum

class {enum}(Enum):
    """The {enum} enum"""
    def __new__(cls, value, doc):
        self = object.__new__(cls)
        self._value_ = value
        self.__doc__ = doc
        return self
'''

ENUMS_ATTR_TEMPLATE = r'''    {name}=c_{enum}.{cpp_name}, """{doc}"""
'''

# list to enforce proper ordering
comment_repls = [
    # first remove explicit cpp references
    (':cpp:func:`(.*?)`', '\\1'),
    (':cpp:enumerator:`(.*?)`', ':py:obj:`\\1`'),
    # introduce proper python references
    ('Term::([a-zA-Z]+)\(([^)]*)\)', ':py:meth:`Term.\\1()`'),
    ('Solver::([a-zA-Z]+)\(([^)]*)\) const', ':py:meth:`Solver.\\1()`'),
    ('Solver::([a-zA-Z]+)\(([^)]*)\)', ':py:meth:`Solver.\\1()`'),
    ('DatatypeConstructor::([a-zA-Z]+)\(([^)]*)\) const', ':py:meth:`DatatypeConstructor.\\1()`'),
    ('DatatypeConstructor::([a-zA-Z]+)\(([^)]*)\)', ':py:meth:`DatatypeConstructor.\\1()`'),
    ('Datatype::([a-zA-Z]+)\(([^)]*)\) const', ':py:meth:`Datatype.\\1()`'),
    ('Datatype::([a-zA-Z]+)\(([^)]*)\)', ':py:meth:`Datatype.\\1()`'),
    ('DatatypeSelector::([a-zA-Z]+)\(([^)]*)\) const', ':py:meth:`DatatypeSelector.\\1()`'),
    ('DatatypeSelector::([a-zA-Z]+)\(([^)]*)\)', ':py:meth:`DatatypeSelector.\\1()`'),
    ('\\\\', '\\\\\\\\'),
]


def reformat_comment(comment):
    # apply replacements from above
    for pat, repl in comment_repls:
        comment = re.sub(pat, repl, comment)
    # remove duplicate lines (e.g. overloads collapse from previous substitutions)
    comment = re.sub('^(?P<line>.*)$\n^(?P=line)$', '\\g<line>', comment, flags=re.MULTILINE)
    return comment


def gen_pxd(parser: EnumParser, file_prefix, header):
    with open(file_prefix + ".pxd", "w") as f:
        for namespace in parser.namespaces:
            for enum in namespace.enums:
                f.write(
                    ENUMS_PXD_TOP_TEMPLATE.format(header=header[len(SOURCE_DIR)
                                                                + 1:],
                                                  enum=enum.name,
                                                  namespace=namespace.name))
                for enumerator in enum.enumerators:
                    f.write("       {},\n".format(enumerator))


def gen_pxi(parser: EnumParser, file_prefix):
    basename = file_prefix[file_prefix.rfind("/") + 1:]
    with open(file_prefix + ".pxi", "w") as f:
        for namespace in parser.namespaces:
            for enum in namespace.enums:
                f.write(ENUMS_PXI_TOP.format(basename=basename,
                                             enum=enum.name))
                for name, value in enum.enumerators.items():
                    doc = reformat_comment(enum.enumerators_doc.get(name, ''))
                    f.write(
                        ENUMS_ATTR_TEMPLATE.format(name=name,
                                                   enum=enum.name,
                                                   cpp_name=name,
                                                   doc=doc))


if __name__ == "__main__":
    parser = argparse.ArgumentParser(
        'Read a header with enums and generate a '
        'corresponding pxd file, with simplified enum names.')
    parser.add_argument('--enums-header',
                        metavar='<ENUMS_HEADER>',
                        help='The header file to read enums from')
    parser.add_argument('--enums-file-prefix',
                        metavar='<ENUMS_FILE_PREFIX>',
                        help='The prefix for the .pxd and .pxi files to write '
                        'the Cython declarations to.',
                        default=DEFAULT_PREFIX)

    args = parser.parse_args()
    enums_header = args.enums_header
    enums_file_prefix = args.enums_file_prefix

    kp = EnumParser()
    kp.parse(enums_header)

    gen_pxd(kp, enums_file_prefix, enums_header)
    gen_pxi(kp, enums_file_prefix)
